#include "postgres.h"

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <errno.h>

inline static _i thread_safe_checker();
inline static _i conn_checker(const char *conn_info);
inline static void conn_reset(PGconn *conn);
static Error *conn(const char *conn_info, PGconn **restrict conn) __mustuse;

inline static Error *query(PGconn *conn, const char *cmd, PGresult **reshdr) __mustuse;
inline static Error *exec(PGconn *conn, const char *cmd, PGresult **reshdr) __mustuse;

inline static Error *query_params(PGconn *conn, const char *cmd, _i params_cnt, const char *const *params_value, PGresult **reshdr) __mustuse;
inline static Error *exec_params(PGconn *conn, const char *cmd, _i params_cnt, const char *const *params_value, PGresult **reshdr) __mustuse;

static Error *prepare(PGconn *conn, const char *cmd, const char *prepare_obj, _i params_cnt, PGresult **reshdr) __mustuse;
inline static Error *query_prepared(PGconn *conn, const char *prepare_obj, _i params_cnt, const char *const *params_value, PGresult **reshdr) __mustuse;
inline static Error *exec_prepared(PGconn *conn, const char *prepare_obj, _i params_cnt, const char *const *params_value, PGresult **reshdr) __mustuse;

static void query_res_parse(PGresult *reshdr, struct QueryRes *query_res);

static void conn_drop(PGconn *conn);
static void res_drop(PGresult *reshdr, struct QueryRes *query_res);

static Error *query_once(char *conn_info, char *cmd, PGresult **reshdr, struct QueryRes *query_res) __mustuse;
static Error *exec_once(char *conn_info, char *cmd) __mustuse;

static void pgpool_init(char *conn_info);
static void pgpool_addjob(void *cmd);

struct Postgres postgres = {
    .thread_safe_checker = thread_safe_checker,

    .conn = conn,
    .conn_checker = conn_checker,
    .conn_reset = conn_reset,

    .query = query,
    .exec = exec,

    .query_params = query_params,
    .exec_params = exec_params,

    .prepare = prepare,
    .query_prepared = query_prepared,
    .exec_prepared = exec_prepared,

    .query_res_parse = query_res_parse,

    .conn_drop = conn_drop,
    .res_drop = res_drop,

    .query_once = query_once,
    .exec_once = exec_once,

    .pool_init = pgpool_init,
    .pool_addjob = pgpool_addjob,
};

/*************
 * pgpool mgmt *
 *************/
//pgpool
#define PGPOOL_SIZ 16
static PGconn *pgpool[PGPOOL_SIZ];
static _i pgpool_stack[PGPOOL_SIZ];
static _i pgpool_stack_header;
static pthread_mutex_t pgpool_lock = PTHREAD_MUTEX_INITIALIZER;

//@param conn_info[in]:
static void
pgpool_init(char *conn_info){
    pgpool_stack_header = PGPOOL_SIZ - 1;
    Error *e;
    _i i;
    for(i = 0; i < PGPOOL_SIZ; ++i){
        pgpool_stack[i] = i;
        __check_fatal(e, conn(conn_info, &pgpool[i]));
    }
}

//@param cmd[in]:
static void
pgpool_addjob(void *cmd){
    Error *e;
    PGresult *reshdr;
    _i keep_id;

    //stack out
    pthread_mutex_lock(&pgpool_lock);

    while (0 > pgpool_stack_header){
        pthread_mutex_unlock(&pgpool_lock);
        utils.msleep(100);
        pthread_mutex_lock(&pgpool_lock);
    }

    keep_id = pgpool_stack[pgpool_stack_header];
    pgpool_stack_header--;

    pthread_mutex_unlock(&pgpool_lock);

    //do it
    if(nil != (e = exec(pgpool[keep_id], cmd, &reshdr))){
        __display_and_clean(e);
    }

    res_drop(reshdr, nil);

    //stack in
    pthread_mutex_lock(&pgpool_lock);
    pgpool_stack[++pgpool_stack_header] = keep_id;
    pthread_mutex_unlock(&pgpool_lock);
}

/************
 * base ops *
 ************/
inline static _i
thread_safe_checker(){
    return PQisthreadsafe();
}

//@param conn_info[in]:
inline static _i
conn_checker(const char *conn_info){
    return PQPING_OK == PQping(conn_info) ? 1: 0;
}

inline static void
conn_reset(PGconn *conn){
    if(nil != conn){
        PQreset(conn);
    }
}

//@param conn_info[in]:
//@param conn[out]:
static Error *
conn(const char *conn_info, PGconn **restrict conn){
    __check_nil(conn_info&&conn);
    Error *e = nil;
    *conn = PQconnectdb(conn_info);

    if(CONNECTION_OK != PQstatus(*conn)){
        e = __err_new(-1, PQerrorMessage(*conn), nil);
        PQfinish(*conn);
    }

    return e;
}

/**************************
 *        exec            *
 **************************/
//@param conn[in]:
//@param cmd[in]:
//@param s[in]: result status type
//@param reshdr[out]:
static Error *
_exec(PGconn *conn, const char *cmd, ExecStatusType s, PGresult **reshdr){
    __check_nil(conn&&cmd&&reshdr);
    Error *e = nil;
    *reshdr = PQexec(conn, cmd);

    if(s != PQresultStatus(*reshdr)){
        e = __err_new(-1, PQresultErrorMessage(*reshdr), nil);
        PQclear(*reshdr);
        *reshdr = nil;
    }

    return e;
}

//@param conn[in]:
//@param cmd[in]:
//@param reshdr[out]:
inline static Error *
query(PGconn *conn, const char *cmd, PGresult **reshdr){
    return _exec(conn, cmd, PGRES_TUPLES_OK, reshdr);
}

//@param conn[in]:
//@param cmd[in]:
//@param reshdr[out]:
inline static Error *
exec(PGconn *conn, const char *cmd, PGresult **reshdr){
    return _exec(conn, cmd, PGRES_COMMAND_OK, reshdr);
}

/***************************
 *  exec with params       *
 ***************************/
//@param conn[in]:
//@param cmd[in]:
//@param s[in]: result status type
//@param params_cnt[in]:
//@param params_value[in]:
//@param reshdr[out]:
static Error *
_exec_params(PGconn *conn, const char *cmd, ExecStatusType s, _i params_cnt, const char *const *params_value, PGresult **reshdr){
    __check_nil(conn&&cmd&&reshdr);
    Error *e = nil;
    *reshdr = PQexecParams(conn, cmd, params_cnt, nil, params_value, nil, nil, 0);

    if(s != PQresultStatus(*reshdr)){
        e = __err_new(-1, PQresultErrorMessage(*reshdr), nil);
        PQclear(*reshdr);
        *reshdr = nil;
    }

    return e;
}

//@param conn[in]:
//@param cmd[in]:
//@param params_cnt[in]:
//@param params_value[in]:
//@param reshdr[out]:
inline static Error *
query_params(PGconn *conn, const char *cmd, _i params_cnt, const char *const *params_value, PGresult **reshdr){
    return _exec_params(conn, cmd, PGRES_TUPLES_OK, params_cnt, params_value, reshdr);
}

//@param conn[in]:
//@param cmd[in]:
//@param params_cnt[in]:
//@param params_value[in]:
//@param reshdr[out]:
inline static Error *
exec_params(PGconn *conn, const char *cmd, _i params_cnt, const char *const *params_value, PGresult **reshdr){
    return _exec_params(conn, cmd, PGRES_COMMAND_OK, params_cnt, params_value, reshdr);
}

/***************************
 *     exec prepared       *
 ***************************/
//@param conn[in]:
//@param cmd[in]: SQL command[s]
//@param params_cnt[in]:
//@param reshdr[out]:
static Error *
prepare(PGconn *conn, const char *cmd, const char *prepare_obj, _i params_cnt, PGresult **reshdr){
    __check_nil(conn&&cmd&&prepare_obj&&reshdr);
    Error *e = nil;
    *reshdr = PQprepare(conn, prepare_obj, cmd, params_cnt, nil);

    if(PGRES_COMMAND_OK != PQresultStatus(*reshdr)){
        e = __err_new(-1, PQresultErrorMessage(*reshdr), nil);
        PQclear(*reshdr);
    }

    return e;
}

//@param conn[in]:
//@param prepare_obj[in]: complied cmd object name
//@param s[in]: result status type
//@param params_cnt[in]:
//@param params_value[in]:
//@param reshdr[out]:
static Error *
_exec_prapared(PGconn *conn, const char *prepare_obj, ExecStatusType s, _i params_cnt, const char *const *params_value, PGresult **reshdr){
    __check_nil(conn&&prepare_obj&&reshdr);
    Error *e = nil;
    *reshdr = PQexecPrepared(conn, prepare_obj, params_cnt, params_value, nil, nil, 0);

    if(s != PQresultStatus(*reshdr)){
        e = __err_new(-1, PQresultErrorMessage(*reshdr), nil);
        PQclear(*reshdr);
        *reshdr = nil;
    }

    return e;
}

//@param conn[in]:
//@param prepare_obj[in]: complied cmd object name
//@param params_cnt[in]:
//@param params_value[in]:
//@param reshdr[out]:
inline static Error *
query_prepared(PGconn *conn, const char *prepare_obj, _i params_cnt, const char *const *params_value, PGresult **reshdr){
    return _exec_prapared(conn, prepare_obj, PGRES_TUPLES_OK, params_cnt, params_value, reshdr);
}

//@param conn[in]:
//@param prepare_obj[in]: complied cmd object name
//@param params_cnt[in]:
//@param params_value[in]:
//@param reshdr[out]:
inline static Error *
exec_prepared(PGconn *conn, const char *prepare_obj, _i params_cnt, const char *const *params_value, PGresult **reshdr){
    return _exec_prapared(conn, prepare_obj, PGRES_COMMAND_OK, params_cnt, params_value, reshdr);
}

//@param reshdr[in]:
//@param query_res[out]:
static void
query_res_parse(PGresult *reshdr, struct QueryRes *query_res){
    if(!(reshdr&&query_res)) __fatal("params invalid");
    _i i, j;

    query_res->fields_cnt = PQnfields(reshdr);
    if(0 == (query_res->tuples_cnt = PQntuples(reshdr))){
        query_res->fields = nil;
        query_res->tuples = nil;
        return;
    }

    query_res->tuples = __alloc(sizeof(void **) * (query_res->tuples_cnt + query_res->fields_cnt + query_res->tuples_cnt * query_res->fields_cnt));
    query_res->fields = (char **)(query_res->tuples + query_res->tuples_cnt);
    query_res->tuples[0] = query_res->fields + query_res->fields_cnt;

    //get fileds name
    for(i = 0; i < query_res->fields_cnt; ++i){
        query_res->fields[i] = PQfname(reshdr, i);
    }

    //get tuples value
    for(j = 0; j < query_res->tuples_cnt; ++j){
        query_res->tuples[j] = query_res->tuples[0] + j * query_res->fields_cnt;
        for(i = 0; i < query_res->fields_cnt; ++i){
            query_res->tuples[j][i] = PQgetvalue(reshdr, j, i);
        }
    }
}

//@param conn[in]:
static void
conn_drop(PGconn *conn){
    if(nil != conn){
        PQfinish(conn);
    }
}

/*
 * ## query_res 中引用了 reshdr 中的数据，故两者必须同时清理，
 * ## 不能在清理前者后，继续使用后者 !!!
 * @param reshdr[in]:
 * @param query_res[in]:
 */
static void
res_drop(PGresult *reshdr, struct QueryRes *query_res){
    if(nil != reshdr){
        PQclear(reshdr);
    };
    if(nil != query_res && nil != query_res->fields){
        free(query_res->tuples);
    }
}

//@param conn_info[in]:
//@param cmd[in]:
//@param reshdr[out]: free it after query_res!!!
//@param query_res[out]:
static Error *
query_once(char *conn_info, char *cmd, PGresult **reshdr, struct QueryRes *query_res){
    __check_nil(conn_info&&cmd&&reshdr&&query_res);
    PGconn *c = nil;
    Error *e;
    if(nil != (e = conn(conn_info, &c))){
        e = __err_new(-1, "pg conn failed", e);
        goto end;
    }

    if(nil != (e = query(c, cmd, reshdr))){
        e = __err_new(-1, "pg exec failed", e);
        goto end;
    }

    query_res_parse(*reshdr, query_res);

end:
    conn_drop(c);
    return e;
}

//@param conn_info[in]:
//@param cmd[in]:
static Error *
exec_once(char *conn_info, char *cmd){
    __check_nil(conn_info&&cmd);
    PGconn *c = nil;
    PGresult *r = nil;
    Error *e;
    if(nil != (e = conn(conn_info, &c))){
        e = __err_new(-1, "pg conn failed", e);
        goto end;
    }

    if(nil != (e = exec(c, cmd, &r))){
        e = __err_new(-1, "pg exec failed", e);
        goto end;
    }

end:
    res_drop(r, nil);
    conn_drop(c);
    return e;
}

/**************
 * COPY ops   *
 **************/
